package org.lex.depends.core.classloader;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.util.Enumeration;
import java.util.Vector;

import org.apache.commons.io.IOUtils;

public class FileSystemClassLoader extends SecureClassLoader {
	private final File directory;

	public FileSystemClassLoader(File directory) {
		super();
		this.directory = directory;
	}

	public FileSystemClassLoader(File directory, ClassLoader parent) {
		super(parent);
		this.directory = directory;
	}

	/**
	 * Finds and loads the class with the specified name from the URL search
	 * path. Any URLs referring to JAR files are loaded and opened as needed
	 * until the class is found.
	 * 
	 * @param name
	 *            the name of the class
	 * @return the resulting class
	 * @exception ClassNotFoundException
	 *                if the class could not be found
	 */
	protected Class<?> findClass(final String name) throws ClassNotFoundException {
		String path = name.replace('.', '/').concat(".class");
		File file = new File(directory, path);
		if (false == file.exists() || false == file.isFile() || false == file.canRead()) {
			System.err.println(file + " not exists, or not file, or can't read");
			throw new ClassNotFoundException(name);
		} else {
			try {
				return defineClass(name, file);
			} catch (IOException e) {
				throw new ClassNotFoundException(name, e);
			}
		}
	}

	/*
	 * Defines a Class using the class bytes obtained from the specified
	 * Resource. The resulting Class must be resolved before it can be used.
	 */
	private Class<?> defineClass(String name, File file) throws IOException {
		FileInputStream fis = new FileInputStream(file);
		byte[] b = IOUtils.toByteArray(fis);
		IOUtils.closeQuietly(fis);
		CodeSigner[] signers = null;
		CodeSource cs = new CodeSource(file.toURI().toURL(), signers);
		return defineClass(name, b, 0, b.length, cs);
	}

	/**
	 * Finds the resource with the specified name on the URL search path.
	 * 
	 * @param name
	 *            the name of the resource
	 * @return a <code>URL</code> for the resource, or <code>null</code> if the
	 *         resource could not be found.
	 */
	public URL findResource(final String name) {
		File file = new File(directory, name);
		if (false == file.exists()) {
			return null;
		} else {
			try {
				return file.toURI().toURL();
			} catch (MalformedURLException e) {
				e.printStackTrace();
				return null;
			}
		}
	}

	/**
	 * Returns an Enumeration of URLs representing all of the resources on the
	 * URL search path having the specified name.
	 * 
	 * @param name
	 *            the resource name
	 * @exception IOException
	 *                if an I/O exception occurs
	 * @return an <code>Enumeration</code> of <code>URL</code>s
	 */
	public Enumeration<URL> findResources(final String name) throws IOException {
		Vector<URL> urls = new Vector<URL>(1);
		urls.add(findResource(name));
		return urls.elements();
	}
}
